#include "sqlite.h"

#include <QSqlDatabase>
#include <QDebug>
#include <QStringList>
#include <QMessageBox>
#include <QSqlQuery>
#include <QSqlError>
#include <QRegExp>
#include <QDateTime>
#include <QApplication>
#include <QDir>
#include <QFileInfo>
#include <QtEndian>



extern "C" MY_EXPORT PLUGININFO getPluginInfo(void){
	PLUGININFO tmp;
	tmp.shortName = "SQLite";
	tmp.version = PLUGIN_VERSION(0,0,0,1);
	tmp.versionString = VERSION_TO_STRING( tmp.version );
	tmp.description = QObject::tr("Wtyczka do klienta umożliwiająca "
								  "korzystanie z bazy SQLite");
	tmp.author = "Momus";
	tmp.authorEmail = "klimczak.k@gmail.com";
	tmp.homepage = "http://momus.com.pl";
	return tmp;
}

SQLite::SQLite( QObject *parent ):
		IDatabase(parent),
		m_pSettingsInstance(NULL),
		m_pModel(NULL),
		m_pSortModel(NULL)
{
	qDebug()<< "Available database drivers:" << QSqlDatabase::drivers();

	connect(this, SIGNAL(pluginInitiated()), this, SLOT(postInit()));



}

SQLite::~SQLite(){
	if( m_pCore && m_pCore->unregisterSettingPage( m_pSettingsInstance ) ){
		delete m_pSettingsInstance;
	}
	qDebug() << "Baza się niszczy";
}

void SQLite::postInit(){
	if( m_pCore ){
		Core::setInstance( m_pCore );
		m_pSettingsInstance = new SQLiteSD();
		m_pCore->registerSettingPage( m_pSettingsInstance );
	}
}

bool SQLite::connectToDB(){
	QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE",
												"SQLite::Beaver::Plugin");

	QString application = qApp->applicationName();

	QVariant setting = Core::instance()->settings()->value(
			"plugin/SQLite/database", QVariant() );


	if( !setting.isValid() ){
		if( m_databaseName.isEmpty() ){
			qWarning() << "SQLite => filename is empty, using default";
#if defined(Q_WS_WIN)
		db.setDatabaseName( QString("%1/%2/localdb.sqlite").
						arg( QString(qgetenv("APPDATA")),
							 application.toLower()) );
#else
		db.setDatabaseName( QString("%1/.%2/localdb.sqlite").
						arg( QString(getenv("HOME")),
							 application.toLower()) );
#endif

		}else
			db.setDatabaseName( m_databaseName );
	}else{
		db.setDatabaseName( setting.toString() );
	}

	QFileInfo fi( db.databaseName() );
	QDir pathDir( fi.absoluteDir() );

	if( !pathDir.exists() ){
		if( !pathDir.mkpath( pathDir.absolutePath() ) ){
			qWarning() << "SQLITE -> connect2DB -> cannot create 'path'";
		}

	}

	m_databaseName = db.databaseName();



	const QString re("FATAL:  database \"" +
					 m_databaseName + "\" does not exist");

	if( !db.open() ){

		QString textError = db.lastError().databaseText().trimmed();


		QMessageBox::critical(0, tr("Connection error"),
							  tr("Could not connect to database"));
		qWarning()<<textError;
		qCritical("Could not connect to database");


	}

	m_prefix = Core::instance()->settings()->value(
			"plugin/SQLite/prefix", QVariant("beaver_") ).toString();


	initiateDatabase();

	m_pModel = new DataModel( this );
	m_pSortModel = new QSortFilterProxyModel( this );
	m_pSortModel->setDynamicSortFilter( true );

	connect( m_pModel, SIGNAL(markAsReaded(uint,bool)),
			 this, SLOT(markAsReaded(uint,bool)) );
	connect( m_pModel, SIGNAL(markAsDeleted(uint,bool)),
			 this, SLOT(markAsDeleted(uint,bool)) );

	//recreate();
	return true;
}

bool SQLite::disconnectFromDb(){
	QSqlDatabase db = QSqlDatabase::database( "SQLite::Beaver::Plugin",
											  false );
	if( !db.isValid() )
		return false;

	if( !db.isOpen() )
		return true;

	db.close();

	QSqlDatabase::removeDatabase( "SQLite::Beaver::Plugin" );

	return true;
}

bool SQLite::canConnect() const {
	return QSqlDatabase::drivers().contains("QSQLITE");
}

void SQLite::setSSL(bool ssl){
	Q_ASSERT(ssl);
}

bool SQLite::useSSL() const {
	return false;
}

int SQLite::getDefaultPort() const {
	return -1;
}

inline void SQLite::catchError(QString desc, const QSqlQuery &query){
	if ( query.lastError().isValid() )
		qWarning() << desc << query.lastError();
}

bool SQLite::initiateDatabase(){
	QSqlDatabase db = QSqlDatabase::database( "SQLite::Beaver::Plugin",
											  false );
	QSqlQuery query(db);

	query.exec(QString("CREATE TABLE IF NOT EXISTS %1channels "
			   "(id INTEGER CONSTRAINT %1channels_key PRIMARY KEY, "
			   "sourceId INTEGER NOT NULL CONSTRAINT %1channels_key_source"
			   " UNIQUE, "
			   "title varchar(255) NOT NULL, "
			   "link varchar(255) NOT NULL CONSTRAINT %1url_uniq UNIQUE, "
			   "url varchar(255), "
			   "description text, "
			   "favicon varchar(255), "
			   "deleted NUMERIC NOT NULL DEFAULT 0);").arg(m_prefix));

	if( query.lastError().isValid() ){
		catchError("Query1:", query);
		return false;
	}

	query.exec(QString("CREATE TABLE IF NOT EXISTS %1news "
			   "(id INTEGER CONSTRAINT %1news_key PRIMARY KEY, "
			   "sourceId INTEGER NOT NULL CONSTRAINT %1news_key_source UNIQUE, "
			   "channel INTEGER REFERENCES %1channels(id), "
			   "title text NOT NULL, "
			   "description blob NOT NULL, "
			   "org_size integer NOT NULL, "
			   "link varchar(255), "
			   "guid varchar(255), "
			   "pubDate INTEGER NOT NULL, " //timestamp
			   "readed NUMERIC NOT NULL DEFAULT 0, "
			   "deleted NUMERIC NOT NULL DEFAULT 0, "
			   "lastOperation INTEGER NOT NULL DEFAULT 0);").arg(m_prefix));

	if( query.lastError().isValid() ){
		catchError("Query2:", query);
		return false;
	}

	return true;
}


void SQLite::insertItem(const item &newItem){
	QSqlQuery query;
	//FIXME
	//kanał jest wpisany na sztywno
	query.prepare(QString("INSERT INTO %1news (channel, title, description, "
						  "link, guid, pubDate) "
						  "VALUES (1,?,?,?,?, to_timestamp(?));")
				  .arg(m_prefix) );

	query.addBindValue(newItem.title);
	query.addBindValue(newItem.description);
	query.addBindValue(newItem.link);
	query.addBindValue(newItem.guid);
	query.addBindValue(
			QDateTime::fromString(newItem.pubDate, Qt::ISODate).toTime_t());

	query.exec();

	qDebug()<<"insert:"<<query.lastError();
}

void SQLite::markAsReaded(uint id, bool mark){
	QSqlDatabase db = QSqlDatabase::database( "SQLite::Beaver::Plugin",
											  false );
	QSqlQuery query(db);
	query.prepare( QString("UPDATE %1news SET readed=?, lastOperation=? "
						   "WHERE id=?;")
				   .arg(m_prefix) );
	if( mark )
		query.addBindValue(1);
	else
		query.addBindValue(0);

	query.addBindValue( QDateTime::currentDateTime().toTime_t() );
	query.addBindValue( id );

	query.exec();
}

void SQLite::markAsDeleted(uint id, bool mark){
	QSqlDatabase db = QSqlDatabase::database( "SQLite::Beaver::Plugin",
											  false );
	QSqlQuery query(db);
	query.prepare( QString("UPDATE %1news SET deleted=?, lastOperation=? "
						   "WHERE id=?;")
				   .arg(m_prefix) );
	if( mark )
		query.addBindValue(1);
	else
		query.addBindValue(0);

	query.addBindValue( QDateTime::currentDateTime().toTime_t() );
	query.addBindValue( id );

	query.exec();
}

QSortFilterProxyModel* SQLite::getModel(int id){

	QSqlDatabase db = QSqlDatabase::database( "SQLite::Beaver::Plugin",
											  false );
	QSqlQuery query(db);
	query.prepare( QString("SELECT n.id, n.title, n.readed, n.pubDate "
						  "FROM %1news n, %1channels ch WHERE ch.id=n.channel "
						  "AND ch.deleted=0 AND n.channel=%2 AND n.deleted=0;")
				   .arg(m_prefix, QString::number(id)) );
	if( query.exec() )
		m_pModel->fillData( query );

	m_pSortModel->setSourceModel( m_pModel );


	return m_pSortModel;
}


QString SQLite::getText(int id) const{
	QSqlDatabase db = QSqlDatabase::database( "SQLite::Beaver::Plugin",
											  false );
	QSqlQuery query(db);
	query.prepare(QString("SELECT description, org_size FROM %1news WHERE id=?")
				  .arg(m_prefix) );
	query.addBindValue( id );
	if( query.exec() && query.next() ){
		quint32 org_size = qToBigEndian(query.value(1).toUInt());
		QByteArray data = query.value(0).toByteArray();
		data.prepend((const char*)&org_size, sizeof(quint32));
		data = qUncompress(data);
		return QString(data);
	}
	return QString();
}

QList<ChannelsList> SQLite::getCannelsList() const {
	QList<ChannelsList> tmp;

	QSqlDatabase db = QSqlDatabase::database( "SQLite::Beaver::Plugin",
											  false );
	QSqlQuery query(db);

	query.prepare(QString("SELECT ch.id, ch.title, count(n.id) FROM "
						  "%1channels ch, %1news n WHERE n.channel=ch.id "
						  "AND n.deleted=0 AND ch.deleted=0 "
						  "AND n.readed=0 ORDER BY ch.id;")
				  .arg(m_prefix) );

	if( query.exec() ){
		while( query.next() ){
			if( query.value(0).toUInt() == 0 )
				continue;

			ChannelsList entry;
			entry.id = query.value(0).toUInt();
			entry.name = query.value(1).toString();
			entry.unread = query.value(2).toInt();

			tmp.push_back( entry );
		}
	}

	return tmp;
}



void SQLite::recreate(){
	QSqlDatabase db = QSqlDatabase::database( "SQLite::Beaver::Plugin",
											  false );
	QSqlQuery query(db);
	QSqlQuery query2(db);

	query.exec(QString("CREATE TABLE IF NOT EXISTS %1news2 "
			   "(id INTEGER CONSTRAINT news_key PRIMARY KEY, "
			   "channel INTEGER REFERENCES channels(id), "
			   "title text NOT NULL, "
			   "description blob NOT NULL, "
			   "org_size integer NOT NULL, "
			   "link varchar(255), "
			   "guid varchar(255), "
			   "pubDate INTEGER NOT NULL);").arg(m_prefix)); //timestamp

	query.prepare( QString("SELECT channel, title, description, "
						   "link, guid, pubDate FROM %1news;")
				   .arg(m_prefix) );
	if( query.exec() ){
		while(  query.next() ){
			query2.prepare(QString("INSERT INTO %1news2 (channel, title,"
								   " description, org_size, "
								  "link, guid, pubDate) "
								  "VALUES (:channel,:title,:desc,"
								  ":size,:link,:guid,:pubDate);")
						  .arg(m_prefix) );

			query2.bindValue( ":channel" ,query.value(0).toInt() );
			query2.bindValue( ":title",query.value(1).toString());

			QByteArray data = query.value(2).toString().toUtf8();
			QByteArray data2 = qCompress(data, 9);
			quint32 num = 0;
			//tej metody nie ma pod VS, M$ jej zabronił i każe używać memcpy_s
			//której nie ma pod linuksem
			memcpy(&num, data2, sizeof(quint32));
			quint32 org_size = qFromBigEndian(num);

			qDebug() << data2.size() << org_size << org_size - data2.size();
			data2.remove(0,4);

			query2.bindValue(":desc",data2.toBase64());
			query2.bindValue(":size", org_size);

			query2.bindValue(":link", query.value(3).toString() );
			query2.bindValue( ":guid",query.value(4).toString() );
			query2.bindValue( ":pubDate",query.value(5).toInt() );

			if( query2.exec() )
				qDebug() << "OK";
			else
				qDebug() << query.lastError();
		}
	}
}

QT_BEGIN_NAMESPACE
Q_EXPORT_PLUGIN2(SQLite, SQLite)
QT_END_NAMESPACE
